What is newtype-ts?
The newtype-ts package provides utilities for creating and working with newtypes in TypeScript. Newtypes are a way to create distinct types from existing ones without runtime overhead, which can help with type safety and code clarity.
What are newtype-ts's main functionalities?
Creating Newtypes
This feature allows you to create newtypes, which are distinct types derived from existing ones. The example demonstrates creating a newtype for meters and using an isomorphism to wrap and unwrap values.
import { Newtype, iso } from 'newtype-ts';
type Meter = Newtype<{ readonly Meter: unique symbol }, number>;
const meterIso = iso<Meter>();
const distance: Meter = meterIso.wrap(5);
const value: number = meterIso.unwrap(distance);
Using Newtypes with Functions
This feature shows how to use newtypes with functions to ensure type safety. The example defines newtypes for meters and seconds, and a function to calculate speed, ensuring that only the correct types are used.
import { Newtype, iso } from 'newtype-ts';
type Meter = Newtype<{ readonly Meter: unique symbol }, number>;
const meterIso = iso<Meter>();
type Second = Newtype<{ readonly Second: unique symbol }, number>;
const secondIso = iso<Second>();
const speed = (distance: Meter, time: Second): number => meterIso.unwrap(distance) / secondIso.unwrap(time);
const distance: Meter = meterIso.wrap(100);
const time: Second = secondIso.wrap(10);
const result = speed(distance, time); // 10
Combining Newtypes
This feature demonstrates how to combine newtypes to create more complex types. The example shows how to calculate speed from distance and time, and wrap the result in a newtype for speed.
import { Newtype, iso } from 'newtype-ts';
type Meter = Newtype<{ readonly Meter: unique symbol }, number>;
const meterIso = iso<Meter>();
type Second = Newtype<{ readonly Second: unique symbol }, number>;
const secondIso = iso<Second>();
type Speed = Newtype<{ readonly Speed: unique symbol }, number>;
const speedIso = iso<Speed>();
const calculateSpeed = (distance: Meter, time: Second): Speed => speedIso.wrap(meterIso.unwrap(distance) / secondIso.unwrap(time));
const distance: Meter = meterIso.wrap(100);
const time: Second = secondIso.wrap(10);
const speed: Speed = calculateSpeed(distance, time);
Other packages similar to newtype-ts
io-ts
io-ts is a runtime type system for IO decoding/encoding in TypeScript. It provides a way to define types that can be checked at runtime, which is useful for validating external data. Unlike newtype-ts, io-ts focuses on runtime type validation rather than creating distinct types at compile time.
runtypes
runtypes is a library for runtime validation and type checking in TypeScript. It allows you to define types that can be validated at runtime, similar to io-ts. While runtypes provides runtime type safety, newtype-ts focuses on creating distinct types at compile time without runtime overhead.
typescript-is
typescript-is is a TypeScript transformer that allows you to perform type checking at runtime. It provides a way to assert types and validate data at runtime. Unlike newtype-ts, which focuses on compile-time type safety, typescript-is provides runtime type assertions.
Installation
To install the stable version:
npm i newtype-ts
Note. newtype-ts
depends on fp-ts
and monocle-ts
, starting from 0.3.0
you must install both fp-ts
and monocle-ts
manually (fp-ts
and monocle-ts
are listed in peerDependency
)
Motivation
A common programming practice is to define a type whose representation is identical to an existing one but which has a separate identity in the type system.
type USD = number
type EUR = number
const myamount: USD = 1
declare function change(usd: USD): EUR
declare function saveAmount(eur: EUR): void
saveAmount(change(myamount))
saveAmount(myamount)
Usage
Newtypes
Let's define a newtype for the EUR currency
import { Newtype, iso } from 'newtype-ts'
interface EUR extends Newtype<{ readonly EUR: unique symbol }, number> {}
const isoEUR = iso<EUR>()
const myamount = isoEUR.wrap(0.85)
const n = isoEUR.unwrap(myamount)
declare function saveAmount(eur: EUR): void
saveAmount(0.85)
saveAmount(myamount)
By definition a "newtype" must have the exact same runtime representation as the value that it stores, e.g. a value of type EUR
is just a number
at runtime.
For the Iso
type, see the monocle-ts documentation.
Refinements
An Integer
is a refinement of number
import { Newtype, prism } from 'newtype-ts'
interface Integer extends Newtype<{ readonly Integer: unique symbol }, number> {}
const isInteger = (n: number) => Number.isInteger(n)
const prismInteger = prism<Integer>(isInteger)
const oi = prismInteger.getOption(2)
declare function f(i: Integer): void
f(2)
oi.map(f)
For the Prism
type, see the monocle-ts documentation.
Builtin refinements
Char
Integer
Negative
NegativeInteger
NonNegative
NonNegativeInteger
NonPositive
NonPositiveInteger
NonEmptyString
NonZero
NonZeroInteger
Positive
PositiveInteger
import { NonZero, prismNonZero } from 'newtype-ts/lib/NonZero'
const safeDivide = (numerator: number, denominator: NonZero): number => {
return numerator / prismNonZero.reverseGet(denominator)
}
const result = prismNonZero.getOption(2).map(denominator => safeDivide(2, denominator))
TypeScript compatibility
The stable version is tested against TypeScript 3.5.1
newtype-ts version | required typescript version | required fp-ts version | required monocle-ts version |
---|
0.3 | 3.5.1+ | 2.0.0-rc.6+ | 2.0.0-rc.1+ |
<= 0.2.4 | 2.8+ | 1.0.0+ | 1.0.0+ |
Performance
const double = n => n * 2
const doubleEUR = eurIso.modify(double)
Test double(2)
vs doubleEUR(eurIso.wrap(2))
Results (node v8.9.3
)
double x 538,301,203 ops/sec ±0.45% (87 runs sampled)
doubleEUR x 536,575,600 ops/sec ±0.27% (87 runs sampled)
Recipes
How to lift a function
const double = (n: number): number => n * 2
const doubleEUR = eurIso.modify(double)
Documentation